package convertTestsToJUnit;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

import executor.ReflectionExecutor;

import model.TestClusterBuilder;
import model.CUT;
import operations.CreateObject;
import operations.InvokeMethod;
import operations.Operation;
import operations.OperationResult;
import ga.GAInfo;
import ga.Individual;
import instrumentation.TrackerCoverage;
import model.Filter;
public class JUnitTestGenerator  extends TrackerCoverage{
	/** maximum number of operation for each jUnit test */
	private static final int MAX_TEST_LEN = 2000;
	private final GAInfo     gaInfo;
	private final File destDir;
	private final TestSuite suite = new TestSuite();
	private final String algorithm;
	private final int runNO;
	public JUnitTestGenerator(File destDir,GAInfo     gaInfo,String algorithm,int runNO) {
		this.destDir = destDir;
		this.gaInfo=gaInfo;
		this.algorithm=algorithm;
		this.runNO=runNO;
	}
	public void writeSuite() {
		suite.write(gaInfo);
	}
	public void executeSuiteToreduce() {
		TrackerCoverage tracker=this.gaInfo.getTracker();
		 List<Individual> branchCoveredByIndividual = new LinkedList<Individual>(tracker.branchCoveredByIndividual.values()) ;
		 tracker.setTrackerToNull();
		 tracker.getTracker();
		 gaInfo.setTracker(tracker);
		for(Individual tc: branchCoveredByIndividual){
			ReflectionExecutor.executeToReduce(this.gaInfo,tc);
		}
	}


	private class TestSuite {
		
		private static final long serialVersionUID = -209417855781416430L;


		public void write( GAInfo gaInfo) {
			TrackerCoverage tracker=gaInfo.getTracker();
			Map<Integer,Individual> branchCoveredByIndividual = new HashMap<Integer, Individual>(tracker.branchCoveredByIndividual) ;
			
			List<String> testcases= new LinkedList<String>();
			HashSet<Individual> suiteBR= new HashSet<Individual>();
			for (Integer i : branchCoveredByIndividual.keySet()) {
				Individual test=	branchCoveredByIndividual.get(i);

				if(testcases.contains(test.toString()))continue;
				testcases.add(test.toString());
				suiteBR.add(test);
			}

				TestCase test = new TestCase(suiteBR,gaInfo,algorithm,runNO);
				 test.write(gaInfo);

	}
	}
	private class TestCase{
		private final GAInfo     gaInfo;
		HashSet<Individual> suiteBR;
		private  String packageName;
		private final String  algorithm;
		private final int runNO;
		public TestCase(HashSet<Individual> suiteBR,GAInfo     gaInfo,String algorithm,int runNO) {
			this.suiteBR=suiteBR;
			this.gaInfo=gaInfo;	
			this.runNO=runNO;
			{
				this.algorithm=algorithm;
				packageName= gaInfo.getTestCluster().getClassToTest().keySet().iterator().next().toString();
			}
		}
		private String getTestName(String cName,Integer idx) {
			String testName = cName.replace('-', '_');
			testName = testName.replace(' ', '_');
			if(idx != null) testName = testName + "_" + idx;
			return testName +"_"+ this.algorithm+"_run_"+this.runNO+ "_TestCase";
		}
	
		public void write( GAInfo     gaInfo) {
			TestClusterBuilder TestCluster= gaInfo.getTestCluster();
			CUT cut= TestCluster.getCut();
			String cName=cut.getClazz().getName();
			int testLength = 0;
			int currentJUnitTest = 0;
			int currentTestMethod = 0;
			PrintWriter writer = null;
			for(Individual individual: suiteBR){//for (Entry<Integer, Individual> e : tests.entrySet()) {
				try {
					testLength += individual.getSize();
					if(writer == null || testLength > MAX_TEST_LEN) {
						if(writer != null) writeFooterAndClose(writer);
						
						String testName=getTestName( cName,currentJUnitTest++);
						
						File dir = new File(destDir, packageName.replace('.', File.separatorChar));
						dir.mkdirs();

						File testFile = new File(dir, testName + ".java");
						writer = new PrintWriter(testFile);
						
						writeHeader(writer, testName);
					}
					writeTest( individual, writer, ++currentTestMethod);

					} catch (FileNotFoundException exc) {
						System.err.println( "JUnitTestGerenrator"+ "Cannot open the file: " + exc.getMessage()+" "+ exc);
					}
			}
			if(writer != null) writeFooterAndClose(writer);

			return ;

		}
		private void writeHeader(PrintWriter out, String testName) {
			if(!packageName.isEmpty()) {
				out.println("package " + "test" + ";");
				out.println();
			}

			out.println("/** Test Generated by MAMDR */");
			out.println("public class " + testName + " extends junit.framework.TestCase {");
		}

		private void writeFooterAndClose(PrintWriter writer) {
			writer.println("}");
			writer.println();
			writer.close();
		}
		private void writeTest(Individual test, PrintWriter out, int testNumber) {
			Map<String, String[]> refFactory = this.gaInfo.getTestCluster().getRefFactory();
			out.println();

			out.println("\tpublic void test" + testNumber + "() throws Throwable {");
			out.println();
			{	// create variables
//
//				// group references by type
				Map<String, List<String>> map = new HashMap<String, List<String>>();
				for(String ref:refFactory.keySet()){
					List<String> vars = map.get(ref);
					if(vars == null) {
						vars = new ArrayList<String>();
						map.put(ref, vars);
					}
					String[] refrences = refFactory.get(ref);
					for (int i = 0; i < refrences.length; i++) 
						vars.add(refrences[i]);
				}
				for (String type : map.keySet()) {
					out.print("\t\t" + type);

					boolean first = true;
					for (String var : map.get(type)) {
						if(first) first = false;
						else out.print(",");
						Class<?> clazz=Filter.getPrimitiveTypeOrString(type);
						if(Filter.isPrimitiveOrStringType(clazz))
							initial_primitive_vars(var,clazz,out);
						else
							out.print(" " + var + " = null");
					}
					out.println(";");
				}
			}

			final List<Operation> initialCons  = test.getInitialConstructors();
			for(Operation op : initialCons ) {
				if(op.toString().contains("java.util.")) continue;
				write_out_operation(op,out);
			}
			final List<Operation> ops  = test.getStatements();
			for(Operation op : ops ) {
				write_out_operation(op,out);
			}
			out.println("\t}");			
			

		}
		public void initial_primitive_vars(String var,Class<?> clazz,PrintWriter out){
			 if( Boolean.class.equals(Filter.primitiveAndStringType.get(clazz)))
					out.print(" " + var + " = false");
			 if( Byte.class.equals(Filter.primitiveAndStringType.get(clazz)))
				 out.print(" " + var + " = (byte) 0");
			 if( Integer.class.equals(Filter.primitiveAndStringType.get(clazz)))
				 out.print(" " + var + " = (Integer) 0");
			 if( Long.class.equals(Filter.primitiveAndStringType.get(clazz)))
				 out.print(" " + var + " = (Long) 0");
			 if( Float.class.equals(Filter.primitiveAndStringType.get(clazz)))
				 out.print(" " + var + " = (float) 0.0");
			 if( Double.class.equals(Filter.primitiveAndStringType.get(clazz)))
				 out.print(" " + var + " = (double) 0.0");	 
			 if( Character.class.equals(Filter.primitiveAndStringType.get(clazz)))
				 out.print(" " + var + " = ''");
			 if( clazz.equals(String.class))
				 out.print(" " + var + " = null");	 
		}
		public void write_out_operation(Operation op,PrintWriter out){

			OperationResult opResult = (OperationResult) op.getInfo(OperationResult.KEY);
			if(opResult == null) {
				if(op instanceof InvokeMethod || op instanceof CreateObject) {
					out.println("\t\ttry {");
					out.println("\t\t\t" + op + ";");
					out.println("\t\t} catch(Throwable e) {");
					out.println("\t\t\te.printStackTrace();");
					out.println("\t\t}");
				} else {
					out.println("\t\t" + op + ";");
				}

			} else {
				switch (opResult.getStatus()) {
				case NOT_EXECUTED:
					out.println("\t\t//" + op + "; // Not Executed");
					break;

				case PRECONDITION_ERROR:
					out.println("\t\ttry {");
					out.println("\t\t" + op + "; // Precondition Error");
					out.println("\t\t} catch(Throwable e) {");
					out.println("\t\t\te.printStackTrace();");
					out.println("\t\t}");

					break;

				case POSTCONDITION_ERROR:
					out.println("\t\t" + op
							+ "; //FIXME: this was a faulty invocation!");
					break;
				case SUCCESSFUL:
					out.println("\t\t" + op + "; // SUCCESSFUL");
					break;

				case EXCEPTIONAL:
					out.println("\t\ttry {");
					out.println("\t\t\t" + op + ";");

					// check if the exception thrown is public.
					String publicExcClass = getPublicException(opResult
							.getExcClassName());
					if (publicExcClass == null) {
						out.println("\t\t\tfail(\"Expecting a "
								+ opResult.getExcClassName() + "\");");
						out.println("\t\t} catch(" + opResult.getExcClassName()
								+ " e) {");
					} else {
						out.println("\t\t\tfail(\"Expecting a "
								+ opResult.getExcClassName() + "\");");
						out.println("\t\t} catch(" + publicExcClass + " e) {");
						out.println("\t\t\tassertEquals(\""
								+ opResult.getExcClassName()
								+ "\", e.getClass().getName());");
					}
					out.println("\t\t}");
					out.println();

					break;

				}

			}
		}//writetest
		private String getPublicException(String excClassName) {
			try {
				Class<?> excClass = Class.forName(excClassName);

				if(Modifier.isPublic(excClass.getModifiers())) return null;

				do {
					excClass = excClass.getSuperclass();
				} while(!Modifier.isPublic(excClass.getModifiers()));

				return excClass.getName();

			} catch (ClassNotFoundException e) {
				return "java.lang.Throwable";
			}
		}

	}
}
